package main

import (
	"net"
	"fmt"
	"strings"
	"time"
)

/**
准备工作：
	定义一个结构体，用于存放当个用户的信息
	定义一个map，存放用户信息的结构体
	定义一个channel，当有用户连接进来就发送消息
	定义一个channel，当用户主动退出时发送消息
具体流程：
	Manageer() 发送message管道信息 --> Manageer()接收到message信息，遍历在线用户的map,给每一个用户的管道发送信息 --> WriteToClient接收到当前客户端管道发来的信息，将内容发送给客户端
 */

//定义一个结构体，存储单个连接用户的信息
type Client struct {
	//用来发送消息的管道
	C chan string
	//用户名字默认是IP地址
	Name string
	//IP地址
	Addr string
}

//定义一个map来存储单个在线用户的结构体，格式为 map[ip地址] = Clent
//这里我们并没有给他初始化一个值，因为这里只是定义，起到一个全局变量的作用，具体的分配空间，在相关函数里面执行
var onLineMap map[string]Client

//这个管道的作用是用于发送消息的，当接收到这个管道发来的信息时，就遍历在线用户的map，给所有在线用户广播消息
var message = make(chan string)

//这个管道用于当用户主动退出时，发送消息，服务器这边广播用户退出
var isQuit = make(chan bool)

//用户是否发送消息，这个管道用于做超时处理
var isTimeOut = make(chan bool)

//处理用户连接
func HandleConnect(conn net.Conn)  {
	defer conn.Close()
	//获取ip
	cliAddr := conn.RemoteAddr().String()

	//定义一个用户的结构体
	cli := Client{make(chan string), cliAddr, cliAddr}

	//把结构体添加到map
	onLineMap[cliAddr] = cli

	//新开一个协程，专门给当前客户端发送信息
	go WriteToClient(cli, conn)

	//广播某人上线
	message <- makeData(cli, "login")

	//新开一个协程用来接受用户发来的数据
	go func() {
		buf := make([]byte, 1024 *2)
		//加个循环让这个协程不退出，阻塞接受用户信息
		for  {
			n, err := conn.Read(buf)
			if n == 0 {
				isQuit <- true
				fmt.Println("conn Read:", err)
				return
			}
			msg := string(buf[:n])

			//长度小于2时不去减2,防止报错
			if len(msg) > 2 {
				msg = string(buf[:n - 2])
			}

			//这里对用户发来想信判断，做不同的处理
			//获取用户列表，这个不需要广播，只给当前用户发送
			if len(msg) == 3 && msg == "who" {
				conn.Write([]byte("user list:\n"))
				for _, tmp := range onLineMap{
					list := tmp.Addr + ":" + tmp.Name + "\n"
					conn.Write([]byte(list))
				}
			} else if len(msg) >= 8 && msg[:6] == "rename" {
				name := strings.Split(msg, "|")[1]
				cli.Name = name
				onLineMap[cliAddr] = cli
				conn.Write([]byte("rename ok\n"))
			}else {
				//客户端退出会发送换行，这种直接不发送客户端
				if len(msg) > 2 {
					message <- makeData(cli, msg)
				}
			}
			isTimeOut <- true
		}
	}()

	for {
		select {
		case <- isQuit:
			message <- makeData(cli, "exit")
			delete(onLineMap, cliAddr)
		case <- isTimeOut:
		case <- time.After(time.Second * 30):
			message <- makeData(cli, "timeout")
			delete(onLineMap, cliAddr)
			fmt.Println("超时")
		}
	}
}

//组合数据
func makeData(client Client, msg string) (str string)  {
	str = "[" + client.Addr + "]" + client.Name + ": " + msg
	return
}

func closeConn(conn net.Conn)  {
	conn.Close()
}

/**
往每个在线用户的管道发送消息
 */
func Manageer() {
	//这个会比HandleConnect先执行，因为HandleConnect要用户连接时才会触发，所以在这个函数里给map分配空间，用户连接的时候就可以直接使用了，
	//这里是有给map分配空间立即执行了，下边遍历的操作要等message发送过来消息，才会触发，所以，这里并不会报类似于map没有数据无法遍历之类的错误，因为他执行完分配空间的操作
	//后就阻塞的，下边的for 不会立即执行。
	onLineMap = make(map[string]Client)
	for {
		//一开始这里会阻塞，因为没有用户连接不会发送消息
		msg := <- message

		//便利map
		for _, cli := range onLineMap {
			cli.C <- msg
		}
	}
}

//给客户端发送消息
/**
这里一定要特别注意，这里不是执行了一次，而是每个用户连接进来都会执行，因为这个函数是在HandleConnect里以协程方式调用的，所以每一个连接进来，都会新开一个协程，就是有多少个
在线用户，就有多少个这个方法在执行，因为这里边是广播消息的，用的是管道，管道只要不关闭，就会一直阻塞等待信息发送过来，所以这个方法只要管道不关闭，就不会退出，这样就很
容易解释为什么这个函数看似只给当前客户端发消息，其他用户却也能收到信息了，因为在这个用户连接进来之前，先前连接的用户这个方法就已经在阻塞等待他们自己管道给他们发送消息了，
在Manageer()里，遍历了所有在线用户的map，给每个成员的结构体都发送了消息，所以，只要是在线的用户，都会收到这个消息
 */
func WriteToClient(cli Client, conn net.Conn)  {
	for msg := range cli.C {
		conn.Write([]byte(msg + "\n"))
	}
}

func main()  {
	//监听
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("listen errr:", err)
		return
	}

	//程序运行完关闭
	defer listen.Close()

	//新开一个协程，用于广播用户连接的消息，具体做法就是遍历在线用户的map，给每一个用户发送消息，就是利用结构体里的C 这个管道发送消息，这个协程一开始是阻塞的，因为刚开始
	//没有用户连接，message这个管道不会发送消息
	go Manageer()

	//主协程要做的就是阻塞，等待用户连接
	for {
		conn, cerr := listen.Accept()
		if cerr != nil {
			fmt.Println("conn err:", cerr)
			//这里不能使用return，使用了return，主协程就退出了，如果本次用户连接失败，就直接跳过，保证程序还在运行
			continue
		}
		
		//新开一个协程处理用户连接
		go HandleConnect(conn)
	}
}